package com.wn.ticket.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wn.ticket.common.JwtUtils;
import com.wn.ticket.common.ResponseEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * @创建人 NST
 * @创建时间 2022/8/30
 * @描述
 */
@Slf4j
@Configuration
@Order(1)
@EnableGlobalMethodSecurity(prePostEnabled = true)//权限认证
public class FrontSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtUtils jwtUtils;
    @Autowired
    private FrontUserDetailsService frontUserDetailsService;

    @Bean //针对用户登录可以自定义配置 账号密码出错分开报错 默认统一报错账号或密码错误
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsService(frontUserDetailsService);
        return daoAuthenticationProvider;
    }
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {//对JwtFilter中jwt认证失败抛出异常的处理器
        return (req, resp, exception) -> {
            log.debug("exception:======>{}", exception);
            ObjectMapper objectMapper = new ObjectMapper();
            String failAuthen = objectMapper.writeValueAsString(new ResponseEntity<String>("701", "jwtFail", exception.getMessage()));
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write(failAuthen);
            resp.getWriter().close();
        };
    }

    @Bean
    public FrontJwtFilter frontJwtFilter(){
        return new FrontJwtFilter(jwtUtils,authenticationFailureHandler());
    }

    public FrontAuthenticationFilter authenticationFilter() throws Exception {
        //构造方法的参数是一个URL，只有该URL的请求才回进入该过滤器
        FrontAuthenticationFilter frontAuthenticationFilter = new FrontAuthenticationFilter("/front/user/login");
        frontAuthenticationFilter.setAuthenticationManager(authenticationManager());
        frontAuthenticationFilter.setAuthenticationSuccessHandler((req, resp, authentication) -> {
            //认证成功处理器
            //获取认证信息，产生jwt放入响应头
            FrontUser principal = (FrontUser) authentication.getPrincipal();
            String jwt = jwtUtils.createJWT(principal.getId(), principal.getUsername());
            String s = new ObjectMapper().writeValueAsString(new ResponseEntity("200","token",jwt));
            resp.setContentType("application/json;charset=UTF-8");//设置响应格式和编码格式
            resp.getWriter().write(s);
            resp.getWriter().close();
        });
        frontAuthenticationFilter.setAuthenticationFailureHandler((req, resp, e) -> {
            //认证失败处理器
            ObjectMapper objectMapper = new ObjectMapper();
            if (e instanceof BadCredentialsException) {
                String failAuthen = objectMapper.writeValueAsString(new ResponseEntity<String>("503", "failAuthen", null));
                resp.setContentType("application/json;charset=UTF-8");
                resp.getWriter().write(failAuthen);
                resp.getWriter().close();
            } else {
                e.printStackTrace();
                String failAuthen = objectMapper.writeValueAsString(new ResponseEntity<String>("503", "系统维护中", null));
                resp.setContentType("application/json;charset=UTF-8");
                resp.getWriter().write(failAuthen);
                resp.getWriter().close();
            }
        });
        return frontAuthenticationFilter;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(frontJwtFilter(), UsernamePasswordAuthenticationFilter.class);
        http.addFilterBefore(authenticationFilter(),FrontJwtFilter.class);
        http.cors();//跨域配置
        http.antMatcher("/front/**") //指定以/ticket/开头的请求使用以下配置
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll()//预检请求直接放行
                .antMatchers("/front/user/login","/front/QRcode/**").permitAll()//登录请求直接放行
                .anyRequest().authenticated()//其他请求都需要认证
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//session创建设置为无状态
                .and().csrf().disable();
        http.formLogin()
                .usernameParameter("account")//设置认证的username的属性名
                .passwordParameter("password");//设置认证的password的属性名
    }

    @Bean
    public CorsFilter corsFilter() {
        //1.添加CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        //放行哪些原始域
        config.addAllowedOriginPattern("*");
        //是否发送Cookie信息
        config.setAllowCredentials(true);
        //放行哪些原始域(请求方式)
        config.addAllowedMethod("*");
        //放行哪些原始域(头部信息)
        config.addAllowedHeader("*");
        //暴露哪些头部信息（因为跨域访问默认不能获取全部头部信息）
        config.addExposedHeader("*");
        //2.添加映射路径
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);
        //3.返回新的CorsFilter.
        return new CorsFilter(configSource);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
